Skip to content

feat: migrate HTTP transport to stateless mode, upgrade go-sdk to v1.7.0 - #162

Draft
iavael wants to merge 3 commits into
prometheus:mainfrom
iavael:copilot/migrate-to-stateless-http-protocol
Draft

feat: migrate HTTP transport to stateless mode, upgrade go-sdk to v1.7.0#162
iavael wants to merge 3 commits into
prometheus:mainfrom
iavael:copilot/migrate-to-stateless-http-protocol

Conversation

@iavael

@iavael iavael commented Jul 28, 2026

Copy link
Copy Markdown

Switches the HTTP MCP transport from stateful (SSE, session-tracked) to stateless mode: each request is an independent JSON round-trip with no session ID validation or server-initiated messages.

Changes

  • go-sdk v1.6.1 → v1.7.0Stateless field on StreamableHTTPOptions was introduced in v1.7.0
  • NewStreamableHTTPHandler — sets Stateless: true and JSONResponse: true; drops the sessionTimeout and KeepAlive parameters (inapplicable in stateless mode)
  • --mcp.session-timeout flag removed — sessions no longer exist; flag would be a no-op
  • WriteTimeout matches prometheus timeout — previously forced to 0 to accommodate never-finishing SSE streams; stateless JSON responses complete within a single round trip
  • --mcp.keepalive-interval flag removed — network connection is no longer streamable, flag would be a no-op

@iavael
iavael requested a review from a team as a code owner July 28, 2026 16:44
Copilot AI review requested due to automatic review settings July 28, 2026 16:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the MCP HTTP transport from stateful (session/SSE-oriented) behavior to stateless JSON request/response mode, enabled via the go-sdk v1.7.0 StreamableHTTPOptions.Stateless support.

Changes:

  • Upgrade github.com/modelcontextprotocol/go-sdk from v1.6.1 to v1.7.0.
  • Switch the HTTP handler to stateless + JSON responses, and remove the now-inapplicable --mcp.session-timeout flag.
  • Re-enable HTTP server WriteTimeout (previously disabled for SSE-style streaming).

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
README.md Updates the Docker example wording to reflect stateless JSON HTTP transport.
pkg/mcp/server.go Configures the go-sdk streamable HTTP handler for stateless application/json request/response behavior.
cmd/prometheus-mcp/main.go Removes session-timeout flag usage, updates handler wiring, and adjusts HTTP server timeout behavior.
go.mod Bumps go-sdk dependency to v1.7.0.
go.sum Updates checksums for the go-sdk version bump.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmd/prometheus-mcp/main.go

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

cmd/prometheus-mcp/main.go:357

  • PR description says the HTTP server WriteTimeout is being "restored to 30s", but the code now sets it to prometheus.timeout + 5s (default 1m5s). Please align the implementation with the stated behavior (set WriteTimeout back to 30s) or update the PR description to reflect the new timeout semantics.
		// request/response cycles. Stateless HTTP transport uses
		// application/json responses that complete in a single round
		// trip, so standard timeouts apply.
		ReadTimeout:  30 * time.Second,
		WriteTimeout: *flagPrometheusTimeout + (5 * time.Second),

@iavael
iavael force-pushed the copilot/migrate-to-stateless-http-protocol branch from d86cfd8 to 291d0e1 Compare July 28, 2026 17:01
@iavael
iavael requested a review from Copilot July 28, 2026 17:02
Signed-off-by: Iavael <905853+iavael@users.noreply.github.com>
@iavael
iavael force-pushed the copilot/migrate-to-stateless-http-protocol branch from 291d0e1 to 9a5640c Compare July 28, 2026 17:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

cmd/prometheus-mcp/main.go:357

  • initHTTPServer sets WriteTimeout to *flagPrometheusTimeout (the Prometheus backend client timeout, default 1m), but the PR description says the HTTP server write timeout is being restored to 30s for stateless JSON request/response. Reusing the Prometheus backend timeout here also couples unrelated concerns and makes the actual HTTP server behavior surprising.
		WriteTimeout: *flagPrometheusTimeout,

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

cmd/prometheus-mcp/main.go:138

  • The --mcp.keepalive-interval help text still talks about “connected MCP sessions” and “idle connections from dropping”, which is confusing now that HTTP is stateless and stdio isn’t a network connection. Reword this flag description to describe keepalives in terms of the client/transport and dead-peer detection.
	flagMcpKeepaliveInterval = kingpin.Flag(
		"mcp.keepalive-interval",
		"Interval for sending keepalive pings to connected MCP sessions."+
			" If the peer fails to respond, the session is closed."+
			" Most useful for stdio transport to prevent idle connections from dropping.",
	).Default("30s").Duration()

pkg/mcp/server.go:246

  • NewStreamableHTTPHandler now depends on go-sdk stateless behavior (Stateless: true + JSONResponse: true), but there’s no test asserting the HTTP handler actually responds with JSON (vs SSE framing) and stays compatible across go-sdk upgrades. Adding a focused httptest-based unit test for the /mcp handler (e.g., response Content-Type: application/json and no session semantics) would help prevent regressions.
	handler := mcp.NewStreamableHTTPHandler(
		func(r *http.Request) *mcp.Server {
			return server
		},
		&mcp.StreamableHTTPOptions{
			Stateless:    true,
			JSONResponse: true,
			Logger:       logger,
		},

@iavael
iavael marked this pull request as draft July 28, 2026 17:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

pkg/mcp/server.go:207

  • KeepAlive support appears to be removed entirely (the SDK ServerOptions no longer sets KeepAlive). While this makes sense for stateless HTTP, it also disables keepalive pings for the stdio transport, which is still long-lived/streaming. If the intent was to drop keepalive only for HTTP, consider keeping KeepAlive configurable (and applying it at least for stdio), or update the docs/PR description to explicitly call out that keepalive was removed for all transports.
		&mcp.ServerOptions{
			Instructions: instrx,
			Logger:       logger.WithGroup("go_sdk_logger"),
			Capabilities: caps,
		},

@iavael

iavael commented Jul 28, 2026

Copy link
Copy Markdown
Author

@copilot is keepalive really necessary for stdio transport?

@iavael
iavael marked this pull request as ready for review July 28, 2026 17:30
@iavael

iavael commented Jul 28, 2026

Copy link
Copy Markdown
Author

@tjhop can you, please, give your review?

@tjhop tjhop left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this PR so fast after the official release! The approach overall is solid and the first-order mechanics of things that need changing in order to do the migration (like the unwiring the keepalive/timeout) are all sound. Thankfully, the transition to stateless is overall pretty easy/transparent to consumers. However, there are some (I think) inaccuracies in some of the implementation vs the literal spec. There are also second-order changes and implications that need to be fixed/addressed. I've left feedback inline where possible, but some of the second-order changes mentioned aren't in the changeset to comment on, so listing here:

  1. this will break some telemetry. right now, the server ready metric gets set during the initialize method, but the new "stateless" version of the spec doesn't have an initialize, so the server ready metric goes dark
  2. we should expand telemetry hooks in middleware.go to capture server/discover method, I think. it's not required for clients to use it, so it shouldn't indicate server readiness, though
  3. the server ready metric will need to be re-homed. it didn't really live in the right place before anyway, as the initialize method required a client to connect, so the server would start up successfully and the metric would indicate "not ready" until the first client connected and completed an initialize. really, the server is "ready" once it's about to kick off serving MCP requests. so I would think that would mean:

Thanks for the help taking this on! Lemme know if you have any questions

Comment on lines -133 to -144
flagMcpKeepaliveInterval = kingpin.Flag(
"mcp.keepalive-interval",
"Interval for sending keepalive pings to connected MCP sessions."+
" If the peer fails to respond, the session is closed."+
" Most useful for HTTP transports to prevent idle connections from dropping.",
).Default("30s").Duration()

flagMcpSessionTimeout = kingpin.Flag(
"mcp.session-timeout",
"Idle session timeout for HTTP transport MCP sessions.",
).Default("10m").Duration()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing flags is user-facing and can cause unexpected breakages in other ways (ie, the binary bombing at starting when provided with unexpected flags that suddenly don't work).

We should keep the flags for now, turn them into no-ops, and deprecate them. Let's leave the flag declarations here and update the help descriptions/README with the deprecation warning. we can still continue to unwire the timeout/keepalive configs

Comment thread pkg/mcp/server.go
SessionTimeout: sessionTimeout,
Logger: logger,
Stateless: true,
JSONResponse: true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSONResponse is independent of Stateless, and not required in order for it to work.

In fact, enabling it actually silently breaks client notification logging -- this switches response away from text/event-stream, which breaks the ability to send the notifications mid-stream/call.

// trip, so standard timeouts apply.
ReadTimeout: 30 * time.Second,
WriteTimeout: 0,
WriteTimeout: *flagPrometheusTimeout,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep the WriteTimeout: 0, we need it disabled to continue to support stream responses.

We should also probably bump IdleTimeout: 120 * time.Second as well -- this should reduce HTTP connection churn for normal clients that are potentially above that idle timeout, such as longer prometheus scrape intervals/LB health checks. The latter of which is arguably more consequential, as I'm realizing now this probably also means potentially sporadic 502s.

Comment on lines +345 to +347
// request/response cycles. Stateless HTTP transport uses
// application/json responses that complete in a single round
// trip, so standard timeouts apply.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread pkg/mcp/server.go
// NewStreamableHTTPHandler creates an HTTP handler for the MCP server using
// stateless HTTP transport. In stateless mode, each request is handled
// independently without session tracking, and responses are returned as
// application/json rather than text/event-stream.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is also inaccurate

Comment thread README.md

```shell
# Streamable HTTP transport (capable of SSE as well)
# Stateless HTTP transport (application/json request/response)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be reduced to a s/Streamable/Stateless/

@iavael
iavael marked this pull request as draft August 5, 2026 02:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants